package nebula

import (
	"errors"
	"fmt"
	"reflect"
	"sync"

	"gddgame.cc/galaxy/satellite"

	"gddgame.cc/galaxy/utils"
)

// 用于处理事件分发，逻辑业务管理
type clientNamespace struct {
	s *Client
	//ns     core.NS
	nsp NSP
	//parent core.NSP
	nss   map[NS]*clientNamespace
	query string

	status      satellite.ConnectStatus
	needConnect bool

	transportType satellite.TransportType
	compress      bool
	event         utils.Event

	acksmu sync.Mutex
	acks   map[int]*utils.Caller
	ids    int
}

func newClientNamespace(s *Client, nsp NSP) *clientNamespace {
	n := clientNamespace{
		s:             s,
		nss:           make(map[NS]*clientNamespace),
		query:         "",
		status:        satellite.DISCONNECTED,
		needConnect:   false,
		transportType: satellite.DefaultTransport,
		compress:      false,
		event:         utils.NewEvent(),
		acksmu:        sync.Mutex{},
		acks:          make(map[int]*utils.Caller),
		ids:           0,
	}
	//n.ns = ns
	//n.parent = nsp
	n.nsp = nsp
	//fmt.Println(nsp)
	return &n
}

func (n *clientNamespace) Path() NSP {
	return n.nsp
}

func (n *clientNamespace) On(event NSE, handler interface{}) error {
	return n.event.On(event, handler)
}

func (n *clientNamespace) ID() string {
	return n.s.ID()
}

func (n *clientNamespace) Connect(query string) {
	if n.status != satellite.RECONNECTING {
		n.status = satellite.CONNECTING
	}
	n.packet(&NsData{Type: N_CONNECT, Data: "/?" + query, NSP: n.nsp})
	n.query = query
	n.needConnect = true
}

func (n *clientNamespace) reconnect() {
	if n.status == satellite.DISCONNECTED || n.status == satellite.DISCONNECTING {
		return
	}
	if !n.needConnect {
		return
	}
	n.status = satellite.RECONNECTING
	n.Connect(n.query)
	for k, _ := range n.nss {
		n.nss[k].reconnect()
	}
}

func (n *clientNamespace) Close() error {
	return n.s.Close()
}

func (n *clientNamespace) Disconnect() error {
	n.status = satellite.DISCONNECTING
	n.needConnect = false
	n.packet(&NsData{Type: N_DISCONNECT})
	return n.destroy("client namespace disconnect")
}

func (n *clientNamespace) Error(err error) {
	n.packet(&NsData{Type: N_ERROR, Data: err})
}

func (n *clientNamespace) Emit(event NSE, args ...interface{}) error {
	packet := &NsData{
		Type: N_EVENT,
	}
	var c *utils.Caller
	if l := len(args); l > 0 {
		lastP := args[l-1]
		fv := reflect.ValueOf(lastP)
		if fv.Kind() == reflect.Func {
			var err error
			c, err = utils.NewCaller(lastP)
			if err != nil {
				return err
			}
			args = args[:l-1]
			n.acksmu.Lock()
			n.acks[n.ids] = c
			packet.Id = n.ids + 1
			n.acksmu.Unlock()
		}
	}
	packet.Data = append([]interface{}{event}, args...)
	n.packet(packet)
	return nil
}

func (n *clientNamespace) App() App {
	return n.s.app
}

func (n *clientNamespace) Of(ns NS) (*clientNamespace, error) {
	return n.of(ns)
}

func (n *clientNamespace) of(ns NS) (*clientNamespace, error) {
	if ns == NSP_DEFAULT {
		return n, nil
	}
	if nsI, ok := n.nss[ns]; ok {
		return nsI, fmt.Errorf("namespace %b already used", ns)
	}
	nsI := newClientNamespace(n.s, append(n.nsp, byte(ns)))
	n.nss[ns] = nsI
	return nsI, nil
}

func (n *clientNamespace) With(nsp NSP) (*clientNamespace, error) {
	if nsp == nil {
		return n, nil
	}
	nn := n
	for _, ns := range nsp {
		nn, _ = nn.of(NS(ns))
		if nn == nil {
			return nil, fmt.Errorf("namespace %s not exist", nsp)
		}
	}
	return nn, nil
}

func (n *clientNamespace) SetCompress(compress bool) {
	n.compress = compress
}

func (n *clientNamespace) onError(err error) {
	_, _ = n.emit("error", err)
}

func (n *clientNamespace) onAck(packet *NsData) {
	var ack = n.acks[packet.Id]
	//fmt.Println(n.acks)
	fv := reflect.ValueOf(ack)
	if fv.Kind() == reflect.Func {
		ack.Call(packet.Data.([]interface{}))
		delete(n.acks, packet.Id)
	} else {
		n.s.Debugf("client bad ack %#v", packet.Id)
	}
}

func (n *clientNamespace) onEvent(packet *NsData) {
	var args = packet.Data.([]interface{})
	event := args[0]
	args = args[1:]

	v, e := n.emit(event, args...)
	if e != nil {
		n.Error(e)
	} else if packet.Id > 0 {
		n.ack(packet.Id)(v...)
	}
}

func (n *clientNamespace) onConnect(success bool) {
	_, _ = n.emit("connect", success)
}

func (n *clientNamespace) onDisconnect() {
	_, _ = n.emit("disconnect")
	n.needConnect = false
	_ = n.destroy("server namespace disconnect")
}

func (n *clientNamespace) onReconnect(success bool) {
	_, _ = n.emit("reconnect", success)
}

func (n *clientNamespace) onProtocol(packet *NsData) {
	n.transportType = satellite.TransportType(packet.Data.(string))
}

func (n *clientNamespace) emit(event interface{}, params ...interface{}) (vs []interface{}, err error) {
	value, err := n.event.Fire(event, params)
	if err != nil {
		return
	}
	if len(value) > 0 {
		vs := make([]interface{}, len(value))
		for i, _ := range value {
			vs[i] = value[i].Interface()
		}
	}
	return
}

func (n *clientNamespace) dispatch(packet *NsData) {
	switch packet.Type {
	case N_CONNECT:
		if n.status == satellite.RECONNECTING {
			n.status = satellite.CONNECTED
			n.onReconnect(true)
		} else {
			n.status = satellite.CONNECTED
			n.onConnect(true)
		}
	case N_EVENT:
		n.onEvent(packet)
	case N_ACK:
		n.onAck(packet)
	case N_DISCONNECT:
		n.status = satellite.DISCONNECTED
		n.onDisconnect()
	case N_ERROR:
		if n.status == satellite.RECONNECTING {
			n.onReconnect(false)
		} else if n.status == satellite.CONNECTING {
			n.onConnect(false)
		} else {
			n.onError(errors.New(packet.Data.(string)))
		}
		//case N_PROTOCOL:
		//	n.onProtocol(packet)
	}
}

func (n *clientNamespace) ack(id int) func(args ...interface{}) {
	var sent = false
	return func(args ...interface{}) {
		// prevent double callbacks
		if sent {
			return
		}
		n.packet(&NsData{
			Id:   id,
			Type: N_ACK,
			Data: args,
		})

		sent = true
	}
}

func (n *clientNamespace) packet(packet *NsData) {
	if packet.NSP == nil || len(packet.NSP) == 0 {
		packet.NSP = n.nsp
	}
	n.s.Packet(packet, n)
}

func (n *clientNamespace) destroy(reason string) error {
	for k, _ := range n.nss {
		if err := n.nss[k].Disconnect(); err != nil {
			return err
		}
	}

	return nil
}
